package org.open.otalk.aidl.packet;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.util.StringUtils;

import android.os.Parcel;
import android.os.Parcelable;

public abstract class OPacket implements Parcelable {
	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flags) {

	}

	// ---------------------------------------------------------------
	protected static final String DEFAULT_LANGUAGE = java.util.Locale
			.getDefault().getLanguage().toLowerCase();

	private static String DEFAULT_XML_NS = null;

	/**
	 * Constant used as packetID to indicate that a packet has no id. To
	 * indicate that a packet has no id set this constant as the packet's id.
	 * When the packet is asked for its id the answer will be <tt>null</tt>.
	 */
	public static final String ID_NOT_AVAILABLE = "ID_NOT_AVAILABLE";

	/**
	 * A prefix helps to make sure that ID's are unique across mutliple
	 * instances.
	 */
	private static String prefix = StringUtils.randomString(5) + "-";

	/**
	 * Keeps track of the current increment, which is appended to the prefix to
	 * forum a unique ID.
	 */
	private static long id = 0;

	private String xmlns = DEFAULT_XML_NS;

	/**
	 * Returns the next unique id. Each id made up of a short alphanumeric
	 * prefix along with a unique numeric value.
	 * 
	 * @return the next id.
	 */
	public static synchronized String nextID() {
		return prefix + Long.toString(id++);
	}

	public static void setDefaultXmlns(String defaultXmlns) {
		DEFAULT_XML_NS = defaultXmlns;
	}

	private String packetID = null;
	private String to = null;
	private String from = null;
	private final List<PacketExtension> packetExtensions = new CopyOnWriteArrayList<PacketExtension>();

	private final Map<String, Object> properties = new HashMap<String, Object>();
	private OXMPPError error = null;

	/**
	 * Returns the unique ID of the packet. The returned value could be
	 * <tt>null</tt> when ID_NOT_AVAILABLE was set as the packet's id.
	 * 
	 * @return the packet's unique ID or <tt>null</tt> if the packet's id is not
	 *         available.
	 */
	public String getPacketID() {
		if (ID_NOT_AVAILABLE.equals(packetID)) {
			return null;
		}

		if (packetID == null) {
			packetID = nextID();
		}
		return packetID;
	}

	/**
	 * Sets the unique ID of the packet. To indicate that a packet has no id
	 * pass the constant ID_NOT_AVAILABLE as the packet's id value.
	 * 
	 * @param packetID
	 *            the unique ID for the packet.
	 */
	public void setPacketID(String packetID) {
		this.packetID = packetID;
	}

	/**
	 * Returns who the packet is being sent "to", or <tt>null</tt> if the value
	 * is not set. The XMPP protocol often makes the "to" attribute optional, so
	 * it does not always need to be set.
	 * <p>
	 * 
	 * The StringUtils class provides several useful methods for dealing with
	 * XMPP addresses such as parsing the
	 * {@link StringUtils#parseBareAddress(String) bare address},
	 * {@link StringUtils#parseName(String) user name},
	 * {@link StringUtils#parseServer(String) server}, and
	 * {@link StringUtils#parseResource(String) resource}.
	 * 
	 * @return who the packet is being sent to, or <tt>null</tt> if the value
	 *         has not been set.
	 */
	public String getTo() {
		return to;
	}

	/**
	 * Sets who the packet is being sent "to". The XMPP protocol often makes the
	 * "to" attribute optional, so it does not always need to be set.
	 * 
	 * @param to
	 *            who the packet is being sent to.
	 */
	public void setTo(String to) {
		this.to = to;
	}

	/**
	 * Returns who the packet is being sent "from" or <tt>null</tt> if the value
	 * is not set. The XMPP protocol often makes the "from" attribute optional,
	 * so it does not always need to be set.
	 * <p>
	 * 
	 * The StringUtils class provides several useful methods for dealing with
	 * XMPP addresses such as parsing the
	 * {@link StringUtils#parseBareAddress(String) bare address},
	 * {@link StringUtils#parseName(String) user name},
	 * {@link StringUtils#parseServer(String) server}, and
	 * {@link StringUtils#parseResource(String) resource}.
	 * 
	 * @return who the packet is being sent from, or <tt>null</tt> if the value
	 *         has not been set.
	 */
	public String getFrom() {
		return from;
	}

	/**
	 * Sets who the packet is being sent "from". The XMPP protocol often makes
	 * the "from" attribute optional, so it does not always need to be set.
	 * 
	 * @param from
	 *            who the packet is being sent to.
	 */
	public void setFrom(String from) {
		this.from = from;
	}

	/**
	 * Returns the error associated with this packet, or <tt>null</tt> if there
	 * are no errors.
	 * 
	 * @return the error sub-packet or <tt>null</tt> if there isn't an error.
	 */
	public OXMPPError getError() {
		return error;
	}

	/**
	 * Sets the error for this packet.
	 * 
	 * @param error
	 *            the error to associate with this packet.
	 */
	public void setError(OXMPPError error) {
		this.error = error;
	}

	/**
	 * Returns an unmodifiable collection of the packet extensions attached to
	 * the packet.
	 * 
	 * @return the packet extensions.
	 */
	public synchronized Collection<PacketExtension> getExtensions() {
		if (packetExtensions == null) {
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(new ArrayList<PacketExtension>(
				packetExtensions));
	}

	/**
	 * Returns the first extension of this packet that has the given namespace.
	 * 
	 * @param namespace
	 *            the namespace of the extension that is desired.
	 * @return the packet extension with the given namespace.
	 */
	public PacketExtension getExtension(String namespace) {
		return getExtension(null, namespace);
	}

	/**
	 * Returns the first packet extension that matches the specified element
	 * name and namespace, or <tt>null</tt> if it doesn't exist. If the provided
	 * elementName is null than only the provided namespace is attempted to be
	 * matched. Packet extensions are are arbitrary XML sub-documents in
	 * standard XMPP packets. By default, a DefaultPacketExtension instance will
	 * be returned for each extension. However, PacketExtensionProvider
	 * instances can be registered with the
	 * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}
	 * class to handle custom parsing. In that case, the type of the Object will
	 * be determined by the provider.
	 * 
	 * @param elementName
	 *            the XML element name of the packet extension. (May be null)
	 * @param namespace
	 *            the XML element namespace of the packet extension.
	 * @return the extension, or <tt>null</tt> if it doesn't exist.
	 */
	public PacketExtension getExtension(String elementName, String namespace) {
		if (namespace == null) {
			return null;
		}
		for (PacketExtension ext : packetExtensions) {
			if ((elementName == null || elementName
					.equals(ext.getElementName()))
					&& namespace.equals(ext.getNamespace())) {
				return ext;
			}
		}
		return null;
	}

	/**
	 * Adds a packet extension to the packet.
	 * 
	 * @param extension
	 *            a packet extension.
	 */
	public void addExtension(PacketExtension extension) {
		packetExtensions.add(extension);
	}

	/**
	 * Removes a packet extension from the packet.
	 * 
	 * @param extension
	 *            the packet extension to remove.
	 */
	public void removeExtension(PacketExtension extension) {
		packetExtensions.remove(extension);
	}

	/**
	 * Returns the packet property with the specified name or <tt>null</tt> if
	 * the property doesn't exist. Property values that were orginally
	 * primitives will be returned as their object equivalent. For example, an
	 * int property will be returned as an Integer, a double as a Double, etc.
	 * 
	 * @param name
	 *            the name of the property.
	 * @return the property, or <tt>null</tt> if the property doesn't exist.
	 */
	public synchronized Object getProperty(String name) {
		if (properties == null) {
			return null;
		}
		return properties.get(name);
	}

	/**
	 * Sets a property with an Object as the value. The value must be
	 * Serializable or an IllegalArgumentException will be thrown.
	 * 
	 * @param name
	 *            the name of the property.
	 * @param value
	 *            the value of the property.
	 */
	public synchronized void setProperty(String name, Object value) {
		if (!(value instanceof Serializable)) {
			throw new IllegalArgumentException("Value must be serialiazble");
		}
		properties.put(name, value);
	}

	/**
	 * Deletes a property.
	 * 
	 * @param name
	 *            the name of the property to delete.
	 */
	public synchronized void deleteProperty(String name) {
		if (properties == null) {
			return;
		}
		properties.remove(name);
	}

	/**
	 * Returns an unmodifiable collection of all the property names that are
	 * set.
	 * 
	 * @return all property names.
	 */
	public synchronized Collection<String> getPropertyNames() {
		if (properties == null) {
			return Collections.emptySet();
		}
		return Collections.unmodifiableSet(new HashSet<String>(properties
				.keySet()));
	}

	/**
	 * Returns the packet as XML. Every concrete extension of Packet must
	 * implement this method. In addition to writing out packet-specific data,
	 * every sub-class should also write out the error and the extensions data
	 * if they are defined.
	 * 
	 * @return the XML format of the packet as a String.
	 */
	public abstract String toXML();

	/**
	 * Returns the extension sub-packets (including properties data) as an XML
	 * String, or the Empty String if there are no packet extensions.
	 * 
	 * @return the extension sub-packets as XML or the Empty String if there are
	 *         no packet extensions.
	 */
	protected synchronized String getExtensionsXML() {
		StringBuilder buf = new StringBuilder();
		// Add in all standard extension sub-packets.
		for (PacketExtension extension : getExtensions()) {
			buf.append(extension.toXML());
		}
		// Add in packet properties.
		if (properties != null && !properties.isEmpty()) {
			buf.append("<properties xmlns=\"http://www.jivesoftware.com/xmlns/xmpp/properties\">");
			// Loop through all properties and write them out.
			for (String name : getPropertyNames()) {
				Object value = getProperty(name);
				buf.append("<property>");
				buf.append("<name>").append(StringUtils.escapeForXML(name))
						.append("</name>");
				buf.append("<value type=\"");
				if (value instanceof Integer) {
					buf.append("integer\">").append(value).append("</value>");
				} else if (value instanceof Long) {
					buf.append("long\">").append(value).append("</value>");
				} else if (value instanceof Float) {
					buf.append("float\">").append(value).append("</value>");
				} else if (value instanceof Double) {
					buf.append("double\">").append(value).append("</value>");
				} else if (value instanceof Boolean) {
					buf.append("boolean\">").append(value).append("</value>");
				} else if (value instanceof String) {
					buf.append("string\">");
					buf.append(StringUtils.escapeForXML((String) value));
					buf.append("</value>");
				}
				// Otherwise, it's a generic Serializable object. Serialized
				// objects are in
				// a binary format, which won't work well inside of XML.
				// Therefore, we base-64
				// encode the binary data before adding it.
				else {
					ByteArrayOutputStream byteStream = null;
					ObjectOutputStream out = null;
					try {
						byteStream = new ByteArrayOutputStream();
						out = new ObjectOutputStream(byteStream);
						out.writeObject(value);
						buf.append("java-object\">");
						String encodedVal = StringUtils.encodeBase64(byteStream
								.toByteArray());
						buf.append(encodedVal).append("</value>");
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						if (out != null) {
							try {
								out.close();
							} catch (Exception e) {
								// Ignore.
							}
						}
						if (byteStream != null) {
							try {
								byteStream.close();
							} catch (Exception e) {
								// Ignore.
							}
						}
					}
				}
				buf.append("</property>");
			}
			buf.append("</properties>");
		}
		return buf.toString();
	}

	public String getXmlns() {
		return this.xmlns;
	}

	/**
	 * Returns the default language used for all messages containing localized
	 * content.
	 * 
	 * @return the default language
	 */
	public static String getDefaultLanguage() {
		return DEFAULT_LANGUAGE;
	}

	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;

		OPacket packet = (OPacket) o;

		if (error != null ? !error.equals(packet.error) : packet.error != null) {
			return false;
		}
		if (from != null ? !from.equals(packet.from) : packet.from != null) {
			return false;
		}
		if (!packetExtensions.equals(packet.packetExtensions)) {
			return false;
		}
		if (packetID != null ? !packetID.equals(packet.packetID)
				: packet.packetID != null) {
			return false;
		}
		if (properties != null ? !properties.equals(packet.properties)
				: packet.properties != null) {
			return false;
		}
		if (to != null ? !to.equals(packet.to) : packet.to != null) {
			return false;
		}
		return !(xmlns != null ? !xmlns.equals(packet.xmlns)
				: packet.xmlns != null);
	}

	public int hashCode() {
		int result;
		result = (xmlns != null ? xmlns.hashCode() : 0);
		result = 31 * result + (packetID != null ? packetID.hashCode() : 0);
		result = 31 * result + (to != null ? to.hashCode() : 0);
		result = 31 * result + (from != null ? from.hashCode() : 0);
		result = 31 * result + packetExtensions.hashCode();
		result = 31 * result + properties.hashCode();
		result = 31 * result + (error != null ? error.hashCode() : 0);
		return result;
	}

	// ---------------------------------------------------------------

}
